import statsmodels.api as sm
from sklearn.ensemble import RandomForestClassifier
from sklearn.multiclass import OneVsRestClassifier
from sklearn.svm import SVC
from sklearn.model_selection import train_test_split
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import matplotlib as mpl
import seaborn as sns


#  1、获取
def get_iris():
    columns = ['sepal length', 'sepal width', 'petal length', 'petal width', 'class']

    url = 'https://archive.ics.uci.edu/ml/machine-learning-databases/iris/iris.data'
    df = pd.read_csv(url, names=columns)
    return df


#  2、检查和探索
def get_check(df):
    print(df['class'].unique())
    print(df.groupby('class').count())

    virginica = df[df['class'] == 'Iris-virginica'].reset_index(drop=True)
    print(virginica.head())
    # print(df.describe())
    print(df.describe(percentiles=[0.2, 0.4, 0.8, 0.9, 0.95]))
    print(df.corr())


def explore(df):
    # matplotlib中文显示方块
    mpl.rcParams['font.sans-serif'] = ['SimHei']  # 指定默认字体
    mpl.rcParams['axes.unicode_minus'] = False  # 解决保存图像是负号'-'显示为方块的问题

    plt.style.use('ggplot')

    # 直方图1
    # fig, ax = plt.subplots(figsize=(6, 4))
    # ax.hist(df['petal width'], color='black')
    # ax.set_ylabel('计数', fontsize=12)
    # ax.set_xlabel('宽度', fontsize=12)
    # plt.title('鸢尾花', fontsize=14, y=1.01)

    # 直方图2
    # fig, ax = plt.subplots(2, 2, figsize=(6, 4))
    #
    # ax[0][0].hist(df['petal width'], color='black')
    # ax[0][0].set_ylabel('计数', fontsize=12)
    # ax[0][0].set_xlabel('宽度', fontsize=12)
    # ax[0][0].set_title('鸢尾花花瓣宽度', fontsize=14, y=1.01)
    #
    # ax[0][1].hist(df['petal length'], color='black')
    # ax[0][1].set_ylabel('计数', fontsize=12)
    # ax[0][1].set_xlabel('宽度', fontsize=12)
    # ax[0][1].set_title('鸢尾花花瓣长度', fontsize=14, y=1.01)
    #
    # ax[1][0].hist(df['sepal width'], color='black')
    # ax[1][0].set_ylabel('计数', fontsize=12)
    # ax[1][0].set_xlabel('宽度', fontsize=12)
    # ax[1][0].set_title('鸢尾花花萼宽度', fontsize=14, y=1.01)
    #
    # ax[1][1].hist(df['sepal length'], color='black')
    # ax[1][1].set_ylabel('计数', fontsize=12)
    # ax[1][1].set_xlabel('宽度', fontsize=12)
    # ax[1][1].set_title('鸢尾花花萼长度', fontsize=14, y=1.01)
    #
    # plt.tight_layout()

    # 散点图
    # fig, ax = plt.subplots(figsize=(6, 6))
    #
    # ax.scatter(df['petal width'], df['petal length'], color='g')
    # ax.set_xlabel('花瓣宽度')
    # ax.set_ylabel('花瓣长度')
    # ax.set_title('花瓣散点图')

    # 条形图
    # fig, ax = plt.subplots(figsize=(6, 6))
    #
    # bar_width = 0.8
    # labels = [x for x in df.columns if 'length' in x or 'width' in x]
    #
    # ver_y = [df[df['class'] == 'Iris-versicolor'][x].mean() for x in labels]
    # vir_y = [df[df['class'] == 'Iris-virginica'][x].mean() for x in labels]
    # set_y = [df[df['class'] == 'Iris-setosa'][x].mean() for x in labels]
    #
    # x = np.arange(len(labels))
    #
    # ax.bar(x, vir_y, bar_width, bottom=set_y, color='darkgrey')
    # ax.bar(x, set_y, bar_width, bottom=ver_y, color='white')
    # ax.bar(x, ver_y, bar_width, color='black')
    #
    # ax.set_xticks(x + (bar_width / 2))
    # ax.set_xticklabels(labels, rotation=-70, fontsize=12)
    # ax.set_title('每个类别中特征的平均测量值', y=1.01)
    # ax.legend(['Virginica', 'Setosa', 'Versicolor'])

    # seaborn 1
    # sns.pairplot(df, hue='class')

    # seaborn 2  箱须图
    fig, ax = plt.subplots(2, 2, figsize=(7, 7))

    sns.set(style='white', palette='muted')
    sns.violinplot(x=df['class'], y=df['sepal length'], ax=ax[0, 0])
    sns.violinplot(x=df['class'], y=df['sepal width'], ax=ax[0, 1])
    sns.violinplot(x=df['class'], y=df['petal length'], ax=ax[1, 0])
    sns.violinplot(x=df['class'], y=df['petal width'], ax=ax[1, 1])

    fig.suptitle('Violin Plots', fontsize=16, y=1.03)
    for i in ax.flat:
        plt.setp(i.get_xticklabels(), rotation=-90)

    fig.tight_layout()

    plt.show()


#  3、清理和准备
def prepair(df):
    df['class'] = df['class'].map({'Iris-versicolor': 'VES',
                                   'Iris-virginica': 'VIR',
                                   'Iris-setosa': 'SET'})
    df['wide petal'] = df['petal width'].apply(lambda x: 1 if x >= 1.3 else 0)
    # 按行计算
    df['petal area'] = df.apply(lambda x: x['petal length'] * x['petal width'], axis=1)

    df_new = df.applymap(lambda v: np.log(v) if isinstance(v, float) else v)

    print(df.head())
    # print(df_new.head())

    # 数据概述
    print(df.groupby('class').describe())
    print(df.groupby('petal width')['class'].unique().to_frame())
    original_stats = df.groupby('class')['petal width'].agg({'间距': lambda x: x.max() - x.min(),
                                                             '最大值': np.max,
                                                             '最小值': np.min})
    print(original_stats)

    def my_agg(x):
        names = {
            'delta': x['petal width'].max() - x['petal width'].min(),
            'max': x['petal width'].max(),
            'min': x['petal width'].min(),
        }
        return pd.Series(names)

    stats = df.groupby(['class']).apply(my_agg)
    stats.columns = ['间距', '最大值', '最小值']
    print(stats)

    # stats = df.groupby(by=['class'])['petal width'].agg(get_max, np.max, np.min)
    stats = df.groupby(by=['class'])['petal width'].agg(['max', 'min'])
    stats.columns = ['最大值', '最小值']
    print(stats)


#  4、建模
def statsmodel_ols(df):
    """
    线性回归
    :param df:
    :return:
    """
    # matplotlib中文显示方块
    mpl.rcParams['font.sans-serif'] = ['SimHei']  # 指定默认字体
    mpl.rcParams['axes.unicode_minus'] = False  # 解决保存图像是负号'-'显示为方块的问题

    y = df['sepal length'][: 50]
    x = df['sepal width'][: 50]
    train_x = sm.add_constant(x)

    clf = sm.OLS(y, train_x).fit()
    # 跟R拟合返回的结果形式一样
    print(clf.summary())

    # 画图
    fig, ax = plt.subplots(figsize=(7, 7))

    ax.plot(x, clf.fittedvalues, label='拟合回归线')
    ax.scatter(x, y, label='原始数据', color='r')

    ax.set_xlabel('花萼宽度')
    ax.set_ylabel('花萼长度')
    ax.set_title('Setosa Sepal Width vs. Sepal Length', fontsize=14, y=1.02)
    ax.legend(loc=2)

    plt.show()


def sklearn_random_forest(df):
    """
    随机森林
    :param df:
    :return:
    """
    #  数据处理
    y_dict = {'Iris-versicolor': 0, 'Iris-virginica': 1, 'Iris-setosa': 2}
    sklearn_df = df.copy()

    sklearn_df['class'] = sklearn_df['class'].apply(lambda x: y_dict.get(x))

    x = sklearn_df.iloc[:, :4]
    y = sklearn_df.iloc[:, 4]

    x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=0.3)

    clf = RandomForestClassifier(max_depth=5, n_estimators=10)
    clf.fit(x_train, y_train)

    y_pre = clf.predict(x_test)

    rf = pd.DataFrame(list(zip(y_pre, y_test)), columns=['predicted', 'actual'])
    # rf = pd.DataFrame([y_pre, y_test]).T
    # rf.columns = ['predicted', 'actual']
    rf['correct'] = rf.apply(lambda x: 1 if x['predicted'] == x['actual'] else 0, axis=1)

    accuracy = rf['correct'].sum() / rf['correct'].count()
    print('准确率：{}'.format(accuracy))

    # 特征值贡献度画图
    # 每个维度的贡献率
    feature_importances = clf.feature_importances_
    feature_names = df.columns[: 4]
    # 求特征std
    feature_std = np.std([tree.feature_importances_ for tree in clf.estimators_], axis=0)

    zz = zip(feature_importances, feature_names, feature_std)
    zzs = sorted(zz, key=lambda x: x[0], reverse=True)  # 降序
    imps = [x[0] for x in zzs]
    labels = [x[1] for x in zzs]
    errs = [x[2] for x in zzs]

    plt.bar(range(len(feature_importances)), imps, color='r', yerr=errs, align='center')
    plt.xticks(range(len(feature_importances)), labels)

    plt.show()


def sklearn_svm(df):
    """
    svm
    :param df:
    :return:
    """
    x = df.iloc[:, :4]
    y = df.iloc[:, 4]
    x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=0.3)

    clf = OneVsRestClassifier(SVC(kernel='linear'))
    clf.fit(x_train, y_train)

    y_pred = clf.predict(x_test)

    rf = pd.DataFrame(list(zip(y_pred, y_test)), columns=['predicted', 'actual'])
    rf['correct'] = rf.apply(lambda x: 1 if x['predicted'] == x['actual'] else 0, axis=1)

    accuracy = rf['correct'].sum() / rf['correct'].count()
    print('准确率：{}'.format(accuracy))


#  5、评估
    # 准确率

#  6、部署


#  整体流程
def run():
    #  1、获取数据
    df = get_iris()

    #  2、检查和探索
    # get_check(df)
    # explore(df)

    #  3、清理和准备
    # prepair(df)

    #  4、建模
    #  线性回归
    # statsmodel_ols(df)
    #  随机森林
    sklearn_random_forest(df)
    # svm
    sklearn_svm(df)


if __name__ == '__main__':
    run()
